Setup
First, install some new packages.
if(!require(tmap)){install.packages("tmap")}
if(!require(sf)){install.packages("sf")}
if(!require(maps)){install.packages("maps")}
if(!require(mapsf)){install.packages("mapsf")}
Then, activate the ones we need.
library(tidyverse)
library(readxl)
library(tmap) # Geographical plotting
library(gapminder) # Country level data
library(maps) # Geographical datasets
library(sf) # Shape files (sf) are a common geographical format
library(plotly) # Dynamic graphs
library(mapsf) # A cool new package!
Now, load the data (included in the packages). The World dataset contains geographical data.
data("World")
str(World) # structure of World
Classes ‘sf’ and 'data.frame': 177 obs. of 16 variables:
$ iso_a3 : Factor w/ 177 levels "AFG","AGO","ALB",..: 1 2 3 4 5 6 7 8 9 10 ...
$ name : Factor w/ 177 levels "Afghanistan",..: 1 4 2 166 6 7 5 56 8 9 ...
$ sovereignt : Factor w/ 171 levels "Afghanistan",..: 1 4 2 159 6 7 5 52 8 9 ...
$ continent : Factor w/ 8 levels "Africa","Antarctica",..: 3 1 4 3 8 3 2 7 6 4 ...
$ area : Units: [km^2] num 652860 1246700 27400 71252 2736690 ...
$ pop_est : num 28400000 12799293 3639453 4798491 40913584 ...
$ pop_est_dens: num 43.5 10.3 132.8 67.3 15 ...
$ economy : Factor w/ 7 levels "1. Developed region: G7",..: 7 7 6 6 5 6 6 6 2 2 ...
$ income_grp : Factor w/ 5 levels "1. High income: OECD",..: 5 3 4 2 3 4 2 2 1 1 ...
$ gdp_cap_est : num 784 8618 5993 38408 14027 ...
$ life_exp : num 59.7 NA 77.3 NA 75.9 ...
$ well_being : num 3.8 NA 5.5 NA 6.5 4.3 NA NA 7.2 7.4 ...
$ footprint : num 0.79 NA 2.21 NA 3.14 2.23 NA NA 9.31 6.06 ...
$ inequality : num 0.427 NA 0.165 NA 0.164 ...
$ HPI : num 20.2 NA 36.8 NA 35.2 ...
$ geometry :sfc_MULTIPOLYGON of length 177; first list element: List of 1
..$ :List of 1
.. ..$ : num [1:69, 1:2] 5310471 5408503 5470647 5477147 5541607 ...
..- attr(*, "class")= chr [1:3] "XY" "MULTIPOLYGON" "sfg"
- attr(*, "sf_column")= chr "geometry"
- attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA NA NA NA NA NA ...
..- attr(*, "names")= chr [1:15] "iso_a3" "name" "sovereignt" "continent" ...
data("gapminder")
head(gapminder)
The world data is very rich: it has many attributes (population, economy, etc.). The one column we are interested in is geometry: it contains the data for the maps!
NOTE: there are 2 very useful packages that allow to retrieve data from the World Bank: WID and wbstats: have a look!
We see that a few points are missing for life expectancy, so let’s see if we can complete with the gapminder data (spoiler: not so much!).
This exercise (see below) of grouping two datasets is paramount when plotting geographical content because usually, the geometric properties come from one file and the items we wish to plot come from another file. Thus, the joining step is KEY.
Areas
Maps in which areas are coloured to show some numerical attribute are called choropleths.
With the tmap package
The idea below is to merge the two sources to combine geographical forms and life expectancy. To do that, we need left_join(). But before, we need to create a common variable (i.e., a key) between the two. In World, the term “name” is not so great so let’s change it into “country” (just like in gapminder!). Then we can merge!
names(World)[2] <- "country" # Change the column name
gapminder %>%
left_join(World, by = "country") %>% # Join the two datasets
select(country, lifeExp, pop, geometry)
nrow(gapminder) # Number of instances
[1] 1704
There are missing points. As a rough approximation, let’s just remove them and store the result in data (more on them later).
data <- gapminder %>% # Join and save via the arrow operator <-
left_join(World, by = "country") %>%
na.omit() # Remove rows with missing data
nrow(data) # Number of instances in dataset
[1] 1296
The loss is large because of missing points! We then use the tmap package to create a choropleth.

Some countries are missing => data problem because gapminder is incomplete. Let’s complete it via: https://www.gapminder.org/data/ This yields a much bigger dataset, called “gap2.RData”. Let’s see:
load("gap2.RData")
head(gap2)
It’s highly tidy! Let’s un-tidy it a bit to put it in the original gapminder format.
gap2 <- gap2 %>% pivot_wider(names_from = "indicator", values_from = "value")
Again, we resort to left_join to merge the two data sources.
data <- World %>%
left_join(gap2, by = "country") #%>%
#na.omit()
With the mapsf package
References:
https://riatelab.github.io/mapsf/articles/mapsf.html
https://github.com/riatelab/mapsf
It’s super easy: you just need a shape file with the attribute you want to plot!
data %>%
filter(year == 2018) %>% # Keep only one year (problems otherwise)
sf::st_sf() %>%
mf_map(x = ., var = "lifeExp", type = "choro")

The package does not really use pipes.
map <- data %>%
filter(year == 2018) %>% # Keep only one year (problems otherwise)
sf::st_sf()
my_theme <- list(
name = "mytheme",
bg = "#DDEEFF", # Light blue
fg = "#444444", # Dark grey
mar = c(0, 0, 0, 0),
tab = TRUE,
pos = "center",
inner = TRUE,
line = 1,
cex = .9,
font = 3
)
mf_theme(my_theme)
mf_map(x = map, type = "base")
mf_map(x = map,
add = T, # After you created a map with mf_map, you need this!
var = c("inequality", "well_being"), # Two variables! BUT BE CAREFUL!
type = "prop_choro",
inches = 0.1, # Scale of circles
lwd = 1, # Line width
breaks = "equal",
nbreaks = 6, # Number of colors breaks
leg_title_cex = c(1,1), # Size of legend title
leg_val_cex = c(0.5,0.8), # Size of legend text
pal = "Spectral", # Palette
leg_pos = c("bottomleft", "left"), # Legend position
leg_title= c("Inequality","Well being"), # Legend titles
leg_val_rnd = c(2, 2), # Number of decimals in legends
leg_frame = c(TRUE, TRUE)) # Frames around legend?
mf_layout(title = "Inequality & well-being in the world",
credits = paste0("Sources: MISC"),
frame = TRUE)

With the leaflet package
Next, we move towards interactive maps with the leaflet package. The advantage of leaflet lies in its multiple options that create dynamic plots. The example below is strongly inspired from the reference page: https://rstudio.github.io/leaflet/choropleths.html Thus, looking at the difference between the two codes helps understand how they work.
We are going two use two features:
- a customized color scale;
- labels that appear when the cursor is on the map.
For the scale, we define it manually, both for the colors and for the separation points (bins).
The labels are defined using html functions.
One word on missing data: in the World data, “Czech Rep.” stands for “Czech Republic”, so we recode manually it to fill in a hole. Since it’s a factor in World, we need to use the recode_factor function. For simple text, recode would be sufficient.
if(!require(leaflet)){install.packages("leaflet")} # Install package
library(leaflet) # Load package
datamap <- World %>%
mutate(country = country %>% recode_factor(`Czech Rep.` = "Czech Republic")) %>%
left_join(gap2, by = "country") %>%
filter(year == 2011) # Keep only one year (recent)
palet <- colorBin("YlGnBu", # Yellow-Green-Blue palette
domain = datamap %>% pull(lifeExp), # LifeExp # Domain of labels: lifeExp
bins = c(50, 55, 60, 65, 70, 80, 90)) # Age categories
labels <- sprintf( # Below we define the labels
"<strong>%s</strong><br/>%g Years", # Adding text to label
datamap$country, # We show the country name...
datamap$lifeExp # ... and the life expectancy
) %>% lapply(htmltools::HTML) # Embedded all into html language
We can then move forward with the plot.
Better, but some African countries are still missing… Data problem!
It is possible to integrate leaflet into shiny: https://rstudio.github.io/leaflet/shiny.html You need renderLeaflet() in the server and leafletOutput() in the UI.
Points (with ggplot!)
Points are more easy to handle because they require less information (two coordinates at least plus other attributes like size or color). This is exactly the kind of data type that fits in the ggplot syntax! Below, we show how to combine ggplot with geographical data.
Country level data
While areas must be defined at the country level (complex!), points are simply characterized by two numbers: latitude & longitude. So a list of these two features suffices to plot points. For countries, it is possible to find data online, ex: https://developers.google.com/public-data/docs/canonical/countries_csv
The latitudes and longitudes correspond to the center of the countries.
Below, we create a new dataset with longitudes & latitutes combined with gapminder (updated version).
if(!require(readxl)){install.packages("readxl")} # Install package to read excel files
library(readxl) # Activate package
options(scipen=999) # Remove scientific notation
country_LL <- read_excel("country_LL.xlsx") # Read longitude/latitude data
data2 <- left_join(gap2, country_LL) %>% # Merge two datasets
na.omit() %>% # Remove missing points
mutate(latitude = as.numeric(latitude), # 'Numerize' latitude
longitude = as.numeric(longitude), # 'Numerize' longitude
population = round(population / 10^6,1)) # Scaling population into millions
data2 %>% head()
Be careful, the code below is computationally demanding (be patient). We use the viridis color palette. In ggplot, a small list of maps is available: world, France, Italy, New Zealand, US (and states).
if(!require(scales)){install.packages("scales")}
if(!require(viridis)){install.packages("viridis")} # Package for color gradient
library(viridis)
library(scales)
world <- map_data("world")
g <- ggplot(data = world) + # Data source
geom_polygon(data = world, aes(x = long, # Dark background of the world
y = lat,
group = group)) +
geom_point(data = data2 %>% filter(year == 2018), # Most recent data points
aes(x = longitude, # Classical ggplot syntax!
y = latitude,
size = population,
color = lifeExp,
label = country)) + # Label (for plotly)
scale_color_viridis(option = "magma") # Color scale
ggplotly(g) # Plotly integration
g <- ggplot(data = world) + # Data source
geom_polygon(data = world, aes(x = long, # Dark background of the world
y = lat,
group = group)) +
geom_point(data = data2 %>% filter(year == 2018), # Most recent data points
aes(x = longitude, # Classical ggplot syntax!
y = latitude,
size = population,
color = lifeExp,
label = country)) + # Label (for plotly)
scale_color_viridis(option = "magma") # Color scale
City level data
You can find longitudes & latitudes by searching on Google. Here’s one example for France: https://simplemaps.com/data/fr-cities Worldwide data can be accessed here: https://simplemaps.com/data/world-cities
cities <- read_excel("worldcities.xlsx")
That’s too big. Let’s focus on Germany, Italy, Austria, Slovenia and Switzerland (arbitrarily).
cities_short <- cities %>%
filter(country %in% c("Germany", "Italy", "Switzerland", "Austria", "Slovenia"),
population > 250000) %>%
na.omit()
head(cities_short)
world <- map_data("world")
g <- ggplot(data = world) + # Data source
geom_polygon(data = world, # Dark background of the world
aes(x = long, y = lat, group = group),
size = 0.5, color = "grey") + # Outer lines of polygons
geom_point(data = cities_short, # The points
aes(x = lng,
y = lat,
size = population,
color = country,
label = city)) +
#scale_colour_gradient(low = "#FFFB00", high = "#FF1B00") + # This line is for color = population
scale_colour_manual(values = c("#FFEC00", "#66E234", "#1BCBF7", "#CC44F8", "#FC2361")) +
coord_sf(xlim = c(5, 18), ylim = c(36, 57), expand = FALSE) # Zoom on Europe
ggplotly(g)
Other maps
For points, it’s easy: just use the same coordinate system via longitude & latitude. It’s much more tricky for areas.
The key is to find shape files (one alternative format is GeoJSON, but it is not treated here).
Example: http://datapages.com/gis-map-publishing-program/gis-open-files/global-framework/ Be very careful: some maps are EXTREMELY precise (up to 1m precision). Thus, the files are heavy and the plotting takes a LOT of time. Below, we use 100m precision, which is largely enough.
The full description of all shape file items can be accessed on wikipedia:
- .shp — shape format; the feature geometry itself
- .shx — shape index format; a positional index of the feature geometry to allow seeking forwards and backwards quickly
- .dbf — attribute format; columnar attributes for each shape, in dBase IV format
regions <- sf::st_read("regions-20140306-100m.shp")
Reading layer `regions-20140306-100m' from data source `/Users/coqueret/Documents/IT/COURS/2021/R4DS/S7_extensions/Geocomputing/regions-20140306-100m.shp' using driver `ESRI Shapefile'
Simple feature collection with 27 features and 10 fields
geometry type: MULTIPOLYGON
dimension: XY
bbox: xmin: -61.80976 ymin: -21.38973 xmax: 55.83665 ymax: 51.08984
geographic CRS: WGS 84
str(regions) # Structure of the variable region
Classes ‘sf’ and 'data.frame': 27 obs. of 11 variables:
$ code_insee: chr "42" "72" "83" "25" ...
$ nom : chr "Alsace" "Aquitaine" "Auvergne" "Basse-Normandie" ...
$ nom_cl : chr "Strasbourg" "Bordeaux" "Clermont-Ferrand" "Caen" ...
$ insee_cl : chr "67482" "33063" "63113" "14118" ...
$ nuts2 : chr "FR42" "FR61" "FR72" "FR25" ...
$ iso3166_2 : chr NA NA NA NA ...
$ wikipedia : chr "fr:Alsace" "fr:Aquitaine" "fr:Auvergne" "fr:Basse-Normandie" ...
$ nb_dep : num 2 5 4 3 4 4 6 4 2 4 ...
$ nb_comm : num 904 2296 1310 1812 2046 ...
$ surf_km2 : num 8328 41818 26172 17786 31752 ...
$ geometry :sfc_MULTIPOLYGON of length 27; first list element: List of 1
..$ :List of 1
.. ..$ : num [1:760, 1:2] 7.43 7.43 7.42 7.42 7.4 ...
..- attr(*, "class")= chr [1:3] "XY" "MULTIPOLYGON" "sfg"
- attr(*, "sf_column")= chr "geometry"
- attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA NA NA NA NA NA
..- attr(*, "names")= chr [1:10] "code_insee" "nom" "nom_cl" "insee_cl" ...
That’s complicated. The only important column for now is the name of the region which is easy to recognize. Below, we plot a choropleth where the color is link to the area of the region (bigger = darker).
regions %>%
tm_shape() +
tm_polygons("surf_km2")

That’s prety ugly. Let’s remove the regions outside the mainland.
regions %>%
filter(! (nom %in% c("Guyane", "Mayotte", "Martinique", "Guadeloupe", "La Réunion"))) %>%
tm_shape() +
tm_polygons("surf_km2", border.col = "white") # With the border of polygons

Below, we change the color palette and plot the number of French “départements” inside each region.
regions %>%
filter(! (nom %in% c("Guyane", "Mayotte", "Martinique", "Guadeloupe", "La Réunion"))) %>%
tm_shape() +
tm_fill("nb_dep", palette = "Spectral") # Without the border

This is static: leaflet integration would be better.
Highcharts
The material below is inspired from the vignette https://cran.r-project.org/web/packages/highcharter/vignettes/charting-maps.html
The highcharter package creates good looking plots. Let’s install it (and activate it).
if(!require(highcharter)){install.packages("highcharter")}
library(highcharter)
The package has a large library of maps. The list is given here: https://code.highcharts.com/mapdata/
If you click on a link, you get the address of the map:

The last part is what you specify to get access to a map:
mapdata <- get_data_from_map(download_map_data("countries/fr/custom/fr-all-mainland"))
trying URL 'https://code.highcharts.com/mapdata/countries/fr/custom/fr-all-mainland.js'
Content type 'text/javascript' length 23676 bytes (23 KB)
==================================================
downloaded 23 KB
head(mapdata)
The information that will be plotted comes from this file. We need to add the value that we want to use for the plot. There are 21 regions in the variable map_data. This bypasses the merging of external data via the left_join() function.
mapdata <- mapdata %>% mutate(value = 1:nrow(mapdata)) # Completely random values
head(mapdata, 20)
Below, we plot the regions of France and the color code is driven by the column “value” defined above.
hcmap("countries/fr/custom/fr-all-mainland", # Source for the map
data = mapdata, # Source for the color code
value = "value", # Column for color code
joinBy = c("name"), # Common name & label
name = "Random Plot", # Name of plot
dataLabels = list(enabled = TRUE, format = "{point.name}"), # Allow labels
borderColor = "#FAFAFA", borderWidth = 0.5, # Border info
tooltip = list(valueDecimals = 1, # Label info
valuePrefix = "Value = ", # Before label
valueSuffix = " Units")) # After label
trying URL 'https://code.highcharts.com/mapdata/countries/fr/custom/fr-all-mainland.js'
Content type 'text/javascript' length 23676 bytes (23 KB)
==================================================
downloaded 23 KB
Fine grain local maps
Now let’s turn to smaller scale maps. The data comes from Kaggle, scrapped from tripadvisor. The useful columns: Longitude & Latitude, the MuseumName, the Rating and the LengthOfVisit.
With leaflet, it’s easy to obtain empty maps as long as you know the longitude & latitude of a particular location. Below: Lyon.
tripadvisor <- read_excel("tripadvisor_museum_US.xlsx") %>% # Importing the data
mutate(Longitude = as.numeric(Longitude), # Getting numbers back
Latitude = as.numeric(Latitude),
Rating = as.numeric(Rating),
LengthOfVisit = as.factor(LengthOfVisit)) %>%
filter(Latitude > 40, Latitude < 41, Longitude > (-74.5), Longitude < (-73.5)) # NY museums only
NAs introduced by coercionNAs introduced by coercion
tripadvisor
leaflet() %>% # An empty map: a good start!
setView(lng = 4.85, lat = 45.75, zoom = 12) %>%
addTiles()
Below, you need to specify lon and lat in the source dataframe. Or you can use the hcaes() function
mapdata2 <- get_data_from_map(download_map_data("countries/us/custom/us-ny-congress-113"))
trying URL 'https://code.highcharts.com/mapdata/countries/us/custom/us-ny-congress-113.js'
Content type 'text/javascript' length 23596 bytes (23 KB)
==================================================
downloaded 23 KB
hcmap("countries/us/custom/us-ny-congress-113") %>%
hc_mapNavigation(enabled = TRUE,
buttonOptions = list(
align = "right",
verticalAlign = "bottom")) %>%
hc_add_series(data = tripadvisor %>% mutate(lon = Longitude, lat = Latitude, name = MuseumName), # Add correct names
type = "mappoint", name = "Museums")
trying URL 'https://code.highcharts.com/mapdata/countries/us/custom/us-ny-congress-113.js'
Content type 'text/javascript' length 23596 bytes (23 KB)
==================================================
downloaded 23 KB
Not as impressive as leaflet, but the map could probably be improved. NOTE: in the example above, the geographical background and the points are unrelated.
A test with GeoJSON
GeoJSON are, with shapefiles the other main standard in geocomputing.
It’s easy to obtain files, e.g., via https://geojson-maps.ash.ms Also, you will need the rgdal package.
africa <- rgdal::readOGR("africa.geo.json")
OGR data source with driver: GeoJSON
Source: "/Users/coqueret/Documents/IT/COURS/2021/R4DS/S7_extensions/Geocomputing/africa.geo.json", layer: "africa.geo"
with 51 features
It has 64 fields
LS0tCnRpdGxlOiAiR2VvY29tcHV0aW5nIHdpdGggUiIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQotLS0KCgojIFNldHVwCgpGaXJzdCwgaW5zdGFsbCBzb21lIG5ldyBwYWNrYWdlcy4KCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CmlmKCFyZXF1aXJlKHRtYXApKXtpbnN0YWxsLnBhY2thZ2VzKCJ0bWFwIil9CmlmKCFyZXF1aXJlKHNmKSl7aW5zdGFsbC5wYWNrYWdlcygic2YiKX0KaWYoIXJlcXVpcmUobWFwcykpe2luc3RhbGwucGFja2FnZXMoIm1hcHMiKX0KaWYoIXJlcXVpcmUobWFwc2YpKXtpbnN0YWxsLnBhY2thZ2VzKCJtYXBzZiIpfQpgYGAKCgpUaGVuLCBhY3RpdmF0ZSB0aGUgb25lcyB3ZSBuZWVkLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShyZWFkeGwpCmxpYnJhcnkodG1hcCkgICAgICAgICMgR2VvZ3JhcGhpY2FsIHBsb3R0aW5nCmxpYnJhcnkoZ2FwbWluZGVyKSAgICMgQ291bnRyeSBsZXZlbCBkYXRhCmxpYnJhcnkobWFwcykgICAgICAgICMgR2VvZ3JhcGhpY2FsIGRhdGFzZXRzCmxpYnJhcnkoc2YpICAgICAgICAgICMgU2hhcGUgZmlsZXMgKHNmKSBhcmUgYSBjb21tb24gZ2VvZ3JhcGhpY2FsIGZvcm1hdCAKbGlicmFyeShwbG90bHkpICAgICAgIyBEeW5hbWljIGdyYXBocwpsaWJyYXJ5KG1hcHNmKSAgICAgICAjIEEgY29vbCBuZXcgcGFja2FnZSEKYGBgCgoKTm93LCBsb2FkIHRoZSBkYXRhIChpbmNsdWRlZCBpbiB0aGUgcGFja2FnZXMpLiBUaGUgV29ybGQgZGF0YXNldCBjb250YWlucyBnZW9ncmFwaGljYWwgZGF0YS4gCgpgYGB7cn0KZGF0YSgiV29ybGQiKSAgICAgICAgIyBGcm9tIHRtYXAhCnN0cihXb3JsZCkgICAgICAgICAgICMgc3RydWN0dXJlIG9mIFdvcmxkCmRhdGEoImdhcG1pbmRlciIpCmhlYWQoZ2FwbWluZGVyKQpgYGAKClRoZSB3b3JsZCBkYXRhIGlzIHZlcnkgcmljaDogaXQgaGFzIG1hbnkgYXR0cmlidXRlcyAocG9wdWxhdGlvbiwgZWNvbm9teSwgZXRjLikuIFRoZSBvbmUgY29sdW1uIHdlIGFyZSBpbnRlcmVzdGVkIGluIGlzICoqZ2VvbWV0cnkqKjogaXQgY29udGFpbnMgdGhlIGRhdGEgZm9yIHRoZSBtYXBzIQoKKipOT1RFKio6IHRoZXJlIGFyZSAyIHZlcnkgdXNlZnVsIHBhY2thZ2VzIHRoYXQgYWxsb3cgdG8gcmV0cmlldmUgZGF0YSBmcm9tIHRoZSBXb3JsZCBCYW5rOiAqKldJRCoqIGFuZCAqKndic3RhdHMqKjogaGF2ZSBhIGxvb2shCgpXZSBzZWUgdGhhdCBhIGZldyBwb2ludHMgYXJlIG1pc3NpbmcgZm9yIGxpZmUgZXhwZWN0YW5jeSwgc28gbGV0J3Mgc2VlIGlmIHdlIGNhbiBjb21wbGV0ZSB3aXRoIHRoZSBnYXBtaW5kZXIgZGF0YSAoc3BvaWxlcjogbm90IHNvIG11Y2ghKS4KCioqVGhpcyBleGVyY2lzZSAoc2VlIGJlbG93KSBvZiBncm91cGluZyB0d28gZGF0YXNldHMgaXMgcGFyYW1vdW50IHdoZW4gcGxvdHRpbmcgZ2VvZ3JhcGhpY2FsIGNvbnRlbnQgYmVjYXVzZSB1c3VhbGx5LCB0aGUgZ2VvbWV0cmljIHByb3BlcnRpZXMgY29tZSBmcm9tIG9uZSBmaWxlIGFuZCB0aGUgaXRlbXMgd2Ugd2lzaCB0byBwbG90IGNvbWUgZnJvbSBhbm90aGVyIGZpbGUuIFRodXMsIHRoZSBqb2luaW5nIHN0ZXAgaXMgS0VZLioqCgojIEFyZWFzCgpNYXBzIGluIHdoaWNoIGFyZWFzIGFyZSBjb2xvdXJlZCB0byBzaG93IHNvbWUgbnVtZXJpY2FsIGF0dHJpYnV0ZSBhcmUgY2FsbGVkICpjaG9yb3BsZXRocyouCgojIyBXaXRoIHRoZSB0bWFwIHBhY2thZ2UKClRoZSBpZGVhIGJlbG93IGlzIHRvIG1lcmdlIHRoZSB0d28gc291cmNlcyB0byBjb21iaW5lIGdlb2dyYXBoaWNhbCBmb3JtcyBhbmQgbGlmZSBleHBlY3RhbmN5LiBUbyBkbyB0aGF0LCB3ZSBuZWVkICoqbGVmdF9qb2luKiooKS4gQnV0IGJlZm9yZSwgd2UgbmVlZCB0byBjcmVhdGUgYSBjb21tb24gdmFyaWFibGUgKGkuZS4sIGEgKmtleSopIGJldHdlZW4gdGhlIHR3by4gSW4gV29ybGQsIHRoZSB0ZXJtICJuYW1lIiBpcyBub3Qgc28gZ3JlYXQgc28gbGV0J3MgY2hhbmdlIGl0IGludG8gImNvdW50cnkiIChqdXN0IGxpa2UgaW4gZ2FwbWluZGVyISkuIFRoZW4gd2UgY2FuIG1lcmdlIQoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpuYW1lcyhXb3JsZClbMl0gPC0gImNvdW50cnkiICAgICAgICAgICAgICAgICAgICAjIENoYW5nZSB0aGUgY29sdW1uIG5hbWUKZ2FwbWluZGVyICU+JSAKICBsZWZ0X2pvaW4oV29ybGQsIGJ5ID0gImNvdW50cnkiKSAlPiUgICAgICAgICAgIyBKb2luIHRoZSB0d28gZGF0YXNldHMKICBzZWxlY3QoY291bnRyeSwgbGlmZUV4cCwgcG9wLCBnZW9tZXRyeSkKbnJvdyhnYXBtaW5kZXIpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBOdW1iZXIgb2YgaW5zdGFuY2VzCmBgYAoKVGhlcmUgYXJlIG1pc3NpbmcgcG9pbnRzLiBBcyBhIHJvdWdoIGFwcHJveGltYXRpb24sIGxldCdzIGp1c3QgcmVtb3ZlIHRoZW0gYW5kIHN0b3JlIHRoZSByZXN1bHQgaW4gKipkYXRhKiogKG1vcmUgb24gdGhlbSBsYXRlcikuIAoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpkYXRhIDwtIGdhcG1pbmRlciAlPiUgICAgICAgICAgICAgICAgICAgICAjIEpvaW4gYW5kIHNhdmUgdmlhIHRoZSBhcnJvdyBvcGVyYXRvciA8LSAKICBsZWZ0X2pvaW4oV29ybGQsIGJ5ID0gImNvdW50cnkiKSAlPiUKICBuYS5vbWl0KCkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBSZW1vdmUgcm93cyB3aXRoIG1pc3NpbmcgZGF0YQpucm93KGRhdGEpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIE51bWJlciBvZiBpbnN0YW5jZXMgaW4gZGF0YXNldApgYGAKClRoZSBsb3NzIGlzIGxhcmdlIGJlY2F1c2Ugb2YgbWlzc2luZyBwb2ludHMhIFdlIHRoZW4gdXNlIHRoZSAqdG1hcCogcGFja2FnZSB0byBjcmVhdGUgYSBjaG9yb3BsZXRoLgoKCmBgYHtyfQpkYXRhICU+JSAKICBmaWx0ZXIoeWVhciA9PSAyMDA3KSAlPiUgICAgIyBLZWVwIG9ubHkgb25lIHllYXIgKHByb2JsZW1zIG90aGVyd2lzZSkKICBzZjo6c3Rfc2YoKSAlPiUgICAgICAgICAgICAgIyBUcmFuc2Zvcm0gZGF0YSBpbnRvIHNoYXBlIGZpbGUgKHNmKSBmb3JtYXQKICB0bV9zaGFwZSgpICsgICAgICAgICAgICAgICAgIyBUcmFuZm9ybXMgdGhlIHNmIGludG8gYSB0bWFwIG9iamVjdAogIHRtX3BvbHlnb25zKCJsaWZlRXhwIiwgICAgICAjIFBsb3QhCiAgICAgICAgICAgICAgYWxwaGEgPSAwLjksCiAgICAgICAgICAgICAgcGFsZXR0ZSA9ICJTcGVjdHJhbCIpICAgICAgCmBgYAoKClNvbWUgY291bnRyaWVzIGFyZSBtaXNzaW5nID0+IGRhdGEgcHJvYmxlbSBiZWNhdXNlIGdhcG1pbmRlciBpcyBpbmNvbXBsZXRlLgpMZXQncyBjb21wbGV0ZSBpdCB2aWE6IGh0dHBzOi8vd3d3LmdhcG1pbmRlci5vcmcvZGF0YS8KVGhpcyB5aWVsZHMgYSBtdWNoIGJpZ2dlciBkYXRhc2V0LCBjYWxsZWQgImdhcDIuUkRhdGEiLiBMZXQncyBzZWU6CgpgYGB7cn0KbG9hZCgiZ2FwMi5SRGF0YSIpCmhlYWQoZ2FwMikKYGBgCgpJdCdzIGhpZ2hseSB0aWR5ISBMZXQncyB1bi10aWR5IGl0IGEgYml0IHRvIHB1dCBpdCBpbiB0aGUgb3JpZ2luYWwgKmdhcG1pbmRlciogZm9ybWF0LiAKCmBgYHtyfQpnYXAyIDwtIGdhcDIgJT4lIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSAiaW5kaWNhdG9yIiwgdmFsdWVzX2Zyb20gPSAidmFsdWUiKQpgYGAKCkFnYWluLCB3ZSByZXNvcnQgdG8gbGVmdF9qb2luIHRvIG1lcmdlIHRoZSB0d28gZGF0YSBzb3VyY2VzLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpkYXRhIDwtIFdvcmxkICU+JSAKICBsZWZ0X2pvaW4oZ2FwMiwgYnkgPSAiY291bnRyeSIpICMlPiUKI25hLm9taXQoKSAKYGBgCgoKIyMgV2l0aCB0aGUgKiptYXBzZioqIHBhY2thZ2UKCioqUmVmZXJlbmNlcyoqOiAgCmh0dHBzOi8vcmlhdGVsYWIuZ2l0aHViLmlvL21hcHNmL2FydGljbGVzL21hcHNmLmh0bWwgIApodHRwczovL2dpdGh1Yi5jb20vcmlhdGVsYWIvbWFwc2YgIAoKSXQncyBzdXBlciBlYXN5OiB5b3UganVzdCBuZWVkIGEgc2hhcGUgZmlsZSB3aXRoIHRoZSBhdHRyaWJ1dGUgeW91IHdhbnQgdG8gcGxvdCEKCmBgYHtyLCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CmRhdGEgJT4lIAogIGZpbHRlcih5ZWFyID09IDIwMTgpICU+JSAgICAjIEtlZXAgb25seSBvbmUgeWVhciAocHJvYmxlbXMgb3RoZXJ3aXNlKQogIHNmOjpzdF9zZigpICU+JQogIG1mX21hcCh4ID0gLiwgdmFyID0gImxpZmVFeHAiLCB0eXBlID0gImNob3JvIikKYGBgCgpUaGUgcGFja2FnZSBkb2VzIG5vdCByZWFsbHkgdXNlIHBpcGVzLgoKYGBge3IsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0KbWFwIDwtIGRhdGEgJT4lIAogIGZpbHRlcih5ZWFyID09IDIwMTgpICU+JSAgICAjIEtlZXAgb25seSBvbmUgeWVhciAocHJvYmxlbXMgb3RoZXJ3aXNlKQogIHNmOjpzdF9zZigpCgpteV90aGVtZSA8LSBsaXN0KAogIG5hbWUgPSAibXl0aGVtZSIsIAogIGJnID0gIiNEREVFRkYiLCAgICAgICAgIyBMaWdodCBibHVlCiAgZmcgPSAiIzQ0NDQ0NCIsICAgICAgICAjIERhcmsgZ3JleQogIG1hciA9IGMoMCwgMCwgMCwgMCksIAogIHRhYiA9IFRSVUUsIAogIHBvcyA9ICJjZW50ZXIiLCAKICBpbm5lciA9IFRSVUUsIAogIGxpbmUgPSAxLCAKICBjZXggPSAuOSwgCiAgZm9udCA9IDMKKQoKbWZfdGhlbWUobXlfdGhlbWUpCm1mX21hcCh4ID0gbWFwLCB0eXBlID0gImJhc2UiKQptZl9tYXAoeCA9IG1hcCwKICAgICAgIGFkZCA9IFQsICAgICAgICAgICAgICMgQWZ0ZXIgeW91IGNyZWF0ZWQgYSBtYXAgd2l0aCBtZl9tYXAsIHlvdSBuZWVkIHRoaXMhCiAgICAgICB2YXIgPSBjKCJpbmVxdWFsaXR5IiwgIndlbGxfYmVpbmciKSwgICAgICAjIFR3byB2YXJpYWJsZXMhIEJVVCBCRSBDQVJFRlVMIQogICAgICAgdHlwZSA9ICJwcm9wX2Nob3JvIiwKICAgICAgIGluY2hlcyA9IDAuMSwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgU2NhbGUgb2YgY2lyY2xlcwogICAgICAgbHdkID0gMSwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBMaW5lIHdpZHRoCiAgICAgICBicmVha3MgPSAiZXF1YWwiLCAKICAgICAgIG5icmVha3MgPSA2LCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgTnVtYmVyIG9mIGNvbG9ycyBicmVha3MKICAgICAgIGxlZ190aXRsZV9jZXggPSBjKDEsMSksICAgICAgICAgICAgICAgICAgICMgU2l6ZSBvZiBsZWdlbmQgdGl0bGUKICAgICAgIGxlZ192YWxfY2V4ID0gYygwLjUsMC44KSwgICAgICAgICAgICAgICAgICMgU2l6ZSBvZiBsZWdlbmQgdGV4dAogICAgICAgcGFsID0gIlNwZWN0cmFsIiwgICAgICAgICAgICAgICAgICAgICAgICAgIyBQYWxldHRlCiAgICAgICBsZWdfcG9zID0gYygiYm90dG9tbGVmdCIsICJsZWZ0IiksICAgICAgICAjIExlZ2VuZCBwb3NpdGlvbgogICAgICAgbGVnX3RpdGxlPSBjKCJJbmVxdWFsaXR5IiwiV2VsbCBiZWluZyIpLCAgIyBMZWdlbmQgdGl0bGVzCiAgICAgICBsZWdfdmFsX3JuZCA9IGMoMiwgMiksICAgICAgICAgICAgICAgICAgICAjIE51bWJlciBvZiBkZWNpbWFscyBpbiBsZWdlbmRzCiAgICAgICBsZWdfZnJhbWUgPSBjKFRSVUUsIFRSVUUpKSAgICAgICAgICAgICAgICAjIEZyYW1lcyBhcm91bmQgbGVnZW5kPwptZl9sYXlvdXQodGl0bGUgPSAiSW5lcXVhbGl0eSAmIHdlbGwtYmVpbmcgaW4gdGhlIHdvcmxkIiwgCiAgICAgICAgICBjcmVkaXRzID0gcGFzdGUwKCJTb3VyY2VzOiBNSVNDIiksIAogICAgICAgICAgZnJhbWUgPSBUUlVFKQpgYGAKCgojIyBXaXRoIHRoZSAqKmxlYWZsZXQqKiBwYWNrYWdlCgpOZXh0LCB3ZSBtb3ZlIHRvd2FyZHMgaW50ZXJhY3RpdmUgbWFwcyB3aXRoIHRoZSAqbGVhZmxldCogcGFja2FnZS4gVGhlIGFkdmFudGFnZSBvZiAqbGVhZmxldCogbGllcyBpbiBpdHMgbXVsdGlwbGUgb3B0aW9ucyB0aGF0IGNyZWF0ZSBkeW5hbWljIHBsb3RzLiBUaGUgZXhhbXBsZSBiZWxvdyBpcyBzdHJvbmdseSBpbnNwaXJlZCBmcm9tIHRoZSByZWZlcmVuY2UgcGFnZTogaHR0cHM6Ly9yc3R1ZGlvLmdpdGh1Yi5pby9sZWFmbGV0L2Nob3JvcGxldGhzLmh0bWwKVGh1cywgbG9va2luZyBhdCB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSB0d28gY29kZXMgaGVscHMgdW5kZXJzdGFuZCBob3cgdGhleSB3b3JrLiAKCldlIGFyZSBnb2luZyB0d28gdXNlIHR3byBmZWF0dXJlczogICAKLSBhIGN1c3RvbWl6ZWQgY29sb3Igc2NhbGU7ICAgIAotIGxhYmVscyB0aGF0IGFwcGVhciB3aGVuIHRoZSBjdXJzb3IgaXMgb24gdGhlIG1hcC4KCkZvciB0aGUgc2NhbGUsIHdlIGRlZmluZSBpdCBtYW51YWxseSwgYm90aCBmb3IgdGhlIGNvbG9ycyBhbmQgZm9yIHRoZSBzZXBhcmF0aW9uIHBvaW50cyAoYmlucykuICAgClRoZSBsYWJlbHMgYXJlIGRlZmluZWQgdXNpbmcgaHRtbCBmdW5jdGlvbnMuCgpPbmUgd29yZCBvbiBtaXNzaW5nIGRhdGE6IGluIHRoZSAqV29ybGQqIGRhdGEsICJDemVjaCBSZXAuIiBzdGFuZHMgZm9yICJDemVjaCBSZXB1YmxpYyIsIHNvIHdlIHJlY29kZSBtYW51YWxseSBpdCB0byBmaWxsIGluIGEgaG9sZS4gU2luY2UgaXQncyBhIGZhY3RvciBpbiAqV29ybGQqLCB3ZSBuZWVkIHRvIHVzZSB0aGUgKipyZWNvZGVfZmFjdG9yKiogZnVuY3Rpb24uIEZvciBzaW1wbGUgdGV4dCwgKipyZWNvZGUqKiB3b3VsZCBiZSBzdWZmaWNpZW50LgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQppZighcmVxdWlyZShsZWFmbGV0KSl7aW5zdGFsbC5wYWNrYWdlcygibGVhZmxldCIpfSAgICAgICAgICAgICAgICAgICMgSW5zdGFsbCBwYWNrYWdlCmxpYnJhcnkobGVhZmxldCkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBMb2FkIHBhY2thZ2UKZGF0YW1hcCA8LSBXb3JsZCAlPiUgCiAgbXV0YXRlKGNvdW50cnkgPSBjb3VudHJ5ICU+JSByZWNvZGVfZmFjdG9yKGBDemVjaCBSZXAuYCA9ICJDemVjaCBSZXB1YmxpYyIpKSAlPiUKICBsZWZ0X2pvaW4oZ2FwMiwgYnkgPSAiY291bnRyeSIpICU+JQogIGZpbHRlcih5ZWFyID09IDIwMTEpICAgICAgICAgICAgICAgICAgICAgICAgICMgS2VlcCBvbmx5IG9uZSB5ZWFyIChyZWNlbnQpCgpwYWxldCA8LSBjb2xvckJpbigiWWxHbkJ1IiwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgWWVsbG93LUdyZWVuLUJsdWUgcGFsZXR0ZQogICAgICAgICAgICAgICAgICBkb21haW4gPSBkYXRhbWFwICU+JSBwdWxsKGxpZmVFeHApLCAjIExpZmVFeHAgICAgICMgRG9tYWluIG9mIGxhYmVsczogbGlmZUV4cAogICAgICAgICAgICAgICAgICBiaW5zID0gYyg1MCwgNTUsIDYwLCA2NSwgNzAsIDgwLCA5MCkpICAgICAgICAgICAgICMgQWdlIGNhdGVnb3JpZXMKCmxhYmVscyA8LSBzcHJpbnRmKCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBCZWxvdyB3ZSBkZWZpbmUgdGhlIGxhYmVscwogICI8c3Ryb25nPiVzPC9zdHJvbmc+PGJyLz4lZyBZZWFycyIsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQWRkaW5nIHRleHQgdG8gbGFiZWwKICBkYXRhbWFwJGNvdW50cnksICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFdlIHNob3cgdGhlIGNvdW50cnkgbmFtZS4uLgogIGRhdGFtYXAkbGlmZUV4cCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgLi4uIGFuZCB0aGUgbGlmZSBleHBlY3RhbmN5CikgJT4lIGxhcHBseShodG1sdG9vbHM6OkhUTUwpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBFbWJlZGRlZCBhbGwgaW50byBodG1sIGxhbmd1YWdlCmBgYAoKV2UgY2FuIHRoZW4gbW92ZSBmb3J3YXJkIHdpdGggdGhlIHBsb3QuCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CmRhdGFtYXAgJT4lIAogIGRhdGEuZnJhbWUoKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgVHVybiBpbnRvIGRhdGFmcmFtZSAodGVjaG5pY2FsKQogIHNmOjpzdF9zZigpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRm9ybWF0IGluIHNmCiAgc3RfdHJhbnNmb3JtKCIraW5pdD1lcHNnOjQzMjYiKSAlPiUgICAgICAgICAgICAgIyBDb252ZXJ0IGluIHBhcnRpY3VsYXIgY29vcmRpbmF0ZSByZWZlcmVuY2UgCiAgbGVhZmxldCgpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBDYWxsIGxlYWZsZXQKICBhZGRQb2x5Z29ucyhmaWxsQ29sb3IgPSB+cGFsZXQobGlmZUV4cCksICAgICAgICAjIENyZWF0ZSB0aGUgbWFwIChjb2xvcmVkIHBvbHlnb25zKQogICAgICAgICAgICAgIHdlaWdodCA9IDIsICAgICAgICAgICAgICAgICAgICAgICAgICMgV2lkdGggb2Ygc2VwYXJhdGlvbiBsaW5lCiAgICAgICAgICAgICAgb3BhY2l0eSA9IDEsICAgICAgICAgICAgICAgICAgICAgICAgIyBPcGFjaXR5IG9mIHNlcGFyYXRpb24gbGluZQogICAgICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwgICAgICAgICAgICAgICAgICAgICMgQ29sb3Igb2Ygc2VwYXJhdGlvbiBsaW5lCiAgICAgICAgICAgICAgZGFzaEFycmF5ID0gIjMiLCAgICAgICAgICAgICAgICAgICAgIyBEYXNoIHNpemUgb2Ygc2VwYXJhdGlvbiBsaW5lCiAgICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSAwLjcsICAgICAgICAgICAgICAgICAgIyBPcGFjaXR5IG9mIHBvbHlnb24gY29sb3JzCiAgICAgICAgICAgICAgaGlnaGxpZ2h0ID0gaGlnaGxpZ2h0T3B0aW9ucyggICAgICAgIyA1IGxpbmVzIGJlbG93IGNvbnRyb2wgdGhlIGN1cnNvciBpbXBhY3QKICAgICAgICAgICAgICAgIHdlaWdodCA9IDIsICAgICAgICAgICAgICAgICAgICAgICAjIFdpZHRoIG9mIGxpbmUKICAgICAgICAgICAgICAgIGNvbG9yID0gIiNFRUVFRUUiLCAgICAgICAgICAgICAgICAjIENvbG9yIG9mIGxpbmUKICAgICAgICAgICAgICAgIGRhc2hBcnJheSA9ICIiLCAgICAgICAgICAgICAgICAgICAjIE5vIGRhc2gKICAgICAgICAgICAgICAgIGZpbGxPcGFjaXR5ID0gMC45LCAgICAgICAgICAgICAgICAjIE9wYWNpdHkKICAgICAgICAgICAgICAgIGJyaW5nVG9Gcm9udCA9IFRSVUUpLAogICAgICAgICAgICAgIGxhYmVsID0gbGFiZWxzLCAgICAgICAgICAgICAgICAgICAgICMgTEFCRUwhIERlZmluZWQgYWJvdmUhCiAgICAgICAgICAgICAgbGFiZWxPcHRpb25zID0gbGFiZWxPcHRpb25zKCAgICAgICAgIyBMYWJlbCBvcHRpb25zIGJlbG93Li4uCiAgICAgICAgICAgICAgICBzdHlsZSA9IGxpc3QoImZvbnQtd2VpZ2h0IiA9ICJub3JtYWwiLCBwYWRkaW5nID0gIjNweCA4cHgiKSwKICAgICAgICAgICAgICAgIHRleHRzaXplID0gIjE1cHgiLAogICAgICAgICAgICAgICAgZGlyZWN0aW9uID0gImF1dG8iKQogICkgJT4lCiAgYWRkTGVnZW5kKHBhbCA9IHBhbGV0LCAgICAgICAgICAgICAgICAgICAgIyBMZWdlbmQ6IGNvbWVzIGZyb20gcGFsZXQgY29sb3JzIGRlZmluZWQgYWJvdmUKICAgICAgICAgICAgdmFsdWVzID0gfmxpZmVFeHAsICAgICAgICAgICAgICAjIFZhbHVlcyBjb21lIGZyb20gbGlmZUV4cCB2YXJpYWJsZQogICAgICAgICAgICBvcGFjaXR5ID0gMC45LCAgICAgICAgICAgICAgICAgICMgT3BhY2l0eSBvZiBsZWdlbmQKICAgICAgICAgICAgdGl0bGUgPSAiTWFwIExlZ2VuZCIsICAgICAgICAgICAjIFRpdGxlIG9mIGxlZ2VuZAogICAgICAgICAgICBwb3NpdGlvbiA9ICJib3R0b21yaWdodCIpICAgICAgICMgUG9zaXRpb24gb2YgbGVnZW5kCmBgYAoKCkJldHRlciwgYnV0IHNvbWUgQWZyaWNhbiBjb3VudHJpZXMgYXJlIHN0aWxsIG1pc3NpbmcuLi4gRGF0YSBwcm9ibGVtIQoKSXQgaXMgcG9zc2libGUgdG8gaW50ZWdyYXRlICpsZWFmbGV0KiBpbnRvICpzaGlueSo6IGh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vbGVhZmxldC9zaGlueS5odG1sCllvdSBuZWVkICoqcmVuZGVyTGVhZmxldCoqKCkgaW4gdGhlIHNlcnZlciBhbmQgKipsZWFmbGV0T3V0cHV0KiooKSBpbiB0aGUgVUkuCgoKIyBQb2ludHMgKHdpdGggZ2dwbG90ISkKClBvaW50cyBhcmUgbW9yZSBlYXN5IHRvIGhhbmRsZSBiZWNhdXNlIHRoZXkgcmVxdWlyZSBsZXNzIGluZm9ybWF0aW9uICh0d28gY29vcmRpbmF0ZXMgYXQgbGVhc3QgcGx1cyBvdGhlciBhdHRyaWJ1dGVzIGxpa2Ugc2l6ZSBvciBjb2xvcikuIFRoaXMgaXMgZXhhY3RseSB0aGUga2luZCBvZiBkYXRhIHR5cGUgdGhhdCBmaXRzIGluIHRoZSAqKmdncGxvdCoqIHN5bnRheCEgQmVsb3csIHdlIHNob3cgaG93IHRvIGNvbWJpbmUgZ2dwbG90IHdpdGggZ2VvZ3JhcGhpY2FsIGRhdGEuCgojIyBDb3VudHJ5IGxldmVsIGRhdGEKCldoaWxlIGFyZWFzIG11c3QgYmUgZGVmaW5lZCBhdCB0aGUgY291bnRyeSBsZXZlbCAoY29tcGxleCEpLCBwb2ludHMgYXJlIHNpbXBseSBjaGFyYWN0ZXJpemVkIGJ5IHR3byBudW1iZXJzOiAqKmxhdGl0dWRlKiogJiAqKmxvbmdpdHVkZSoqLiBTbyBhIGxpc3Qgb2YgdGhlc2UgdHdvIGZlYXR1cmVzIHN1ZmZpY2VzIHRvIHBsb3QgcG9pbnRzLiBGb3IgY291bnRyaWVzLCBpdCBpcyBwb3NzaWJsZSB0byBmaW5kIGRhdGEgb25saW5lLCBleDogaHR0cHM6Ly9kZXZlbG9wZXJzLmdvb2dsZS5jb20vcHVibGljLWRhdGEvZG9jcy9jYW5vbmljYWwvY291bnRyaWVzX2NzdgoKVGhlIGxhdGl0dWRlcyBhbmQgbG9uZ2l0dWRlcyBjb3JyZXNwb25kIHRvIHRoZSBjZW50ZXIgb2YgdGhlIGNvdW50cmllcy4KCkJlbG93LCB3ZSBjcmVhdGUgYSBuZXcgZGF0YXNldCB3aXRoIGxvbmdpdHVkZXMgJiBsYXRpdHV0ZXMgY29tYmluZWQgd2l0aCAqZ2FwbWluZGVyKiAodXBkYXRlZCB2ZXJzaW9uKS4KYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQppZighcmVxdWlyZShyZWFkeGwpKXtpbnN0YWxsLnBhY2thZ2VzKCJyZWFkeGwiKX0gICAgIyBJbnN0YWxsIHBhY2thZ2UgdG8gcmVhZCBleGNlbCBmaWxlcwpsaWJyYXJ5KHJlYWR4bCkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBBY3RpdmF0ZSBwYWNrYWdlCm9wdGlvbnMoc2NpcGVuPTk5OSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFJlbW92ZSBzY2llbnRpZmljIG5vdGF0aW9uCmNvdW50cnlfTEwgPC0gcmVhZF9leGNlbCgiY291bnRyeV9MTC54bHN4IikgICAgICAgICAjIFJlYWQgbG9uZ2l0dWRlL2xhdGl0dWRlIGRhdGEKZGF0YTIgPC0gbGVmdF9qb2luKGdhcDIsIGNvdW50cnlfTEwpICU+JSAgICAgICAgICAgICMgTWVyZ2UgdHdvIGRhdGFzZXRzCiAgbmEub21pdCgpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFJlbW92ZSBtaXNzaW5nIHBvaW50cwogIG11dGF0ZShsYXRpdHVkZSA9IGFzLm51bWVyaWMobGF0aXR1ZGUpLCAgICAgICAgICAgIyAnTnVtZXJpemUnIGxhdGl0dWRlCiAgICAgICAgIGxvbmdpdHVkZSA9IGFzLm51bWVyaWMobG9uZ2l0dWRlKSwgICAgICAgICAjICdOdW1lcml6ZScgbG9uZ2l0dWRlCiAgICAgICAgIHBvcHVsYXRpb24gPSByb3VuZChwb3B1bGF0aW9uIC8gMTBeNiwxKSkgICAjIFNjYWxpbmcgcG9wdWxhdGlvbiBpbnRvIG1pbGxpb25zCmRhdGEyICU+JSBoZWFkKCkKYGBgCgpCZSBjYXJlZnVsLCB0aGUgY29kZSBiZWxvdyBpcyBjb21wdXRhdGlvbmFsbHkgZGVtYW5kaW5nIChiZSBwYXRpZW50KS4gV2UgdXNlIHRoZSAqdmlyaWRpcyogY29sb3IgcGFsZXR0ZS4gCkluICpnZ3Bsb3QqLCBhIHNtYWxsIGxpc3Qgb2YgbWFwcyBpcyBhdmFpbGFibGU6IHdvcmxkLCBGcmFuY2UsIEl0YWx5LCBOZXcgWmVhbGFuZCwgVVMgKGFuZCBzdGF0ZXMpLiAKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KaWYoIXJlcXVpcmUoc2NhbGVzKSl7aW5zdGFsbC5wYWNrYWdlcygic2NhbGVzIil9CmlmKCFyZXF1aXJlKHZpcmlkaXMpKXtpbnN0YWxsLnBhY2thZ2VzKCJ2aXJpZGlzIil9ICMgUGFja2FnZSBmb3IgY29sb3IgZ3JhZGllbnQKbGlicmFyeSh2aXJpZGlzKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIApsaWJyYXJ5KHNjYWxlcykgCndvcmxkIDwtIG1hcF9kYXRhKCJ3b3JsZCIpCmcgPC0gZ2dwbG90KGRhdGEgPSB3b3JsZCkgKyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERhdGEgc291cmNlCiAgZ2VvbV9wb2x5Z29uKGRhdGEgPSB3b3JsZCwgYWVzKHggPSBsb25nLCAgICAgICAgICAgICAgICAjIERhcmsgYmFja2dyb3VuZCBvZiB0aGUgd29ybGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IGxhdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwID0gZ3JvdXApKSArICAgCiAgZ2VvbV9wb2ludChkYXRhID0gZGF0YTIgJT4lIGZpbHRlcih5ZWFyID09IDIwMTgpLCAgICAgICAjIE1vc3QgcmVjZW50IGRhdGEgcG9pbnRzCiAgICAgICAgICAgICBhZXMoeCA9IGxvbmdpdHVkZSwgICAgICAgICAgICAgICAgICAgICAgICAgICAjIENsYXNzaWNhbCBnZ3Bsb3Qgc3ludGF4IQogICAgICAgICAgICAgICAgIHkgPSBsYXRpdHVkZSwgCiAgICAgICAgICAgICAgICAgc2l6ZSA9IHBvcHVsYXRpb24sIAogICAgICAgICAgICAgICAgIGNvbG9yID0gbGlmZUV4cCwgCiAgICAgICAgICAgICAgICAgbGFiZWwgPSBjb3VudHJ5KSkgKyAgICAgICAgICAgICAgICAgICAgICAjIExhYmVsIChmb3IgcGxvdGx5KQogIHNjYWxlX2NvbG9yX3ZpcmlkaXMob3B0aW9uID0gIm1hZ21hIikgICAgICAgICAgICAgICAgICAgIyBDb2xvciBzY2FsZQpnZ3Bsb3RseShnKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBQbG90bHkgaW50ZWdyYXRpb24KYGBgCgojIyBDaXR5IGxldmVsIGRhdGEKCllvdSBjYW4gZmluZCBsb25naXR1ZGVzICYgbGF0aXR1ZGVzIGJ5IHNlYXJjaGluZyBvbiBHb29nbGUuIEhlcmUncyBvbmUgZXhhbXBsZSBmb3IgRnJhbmNlOgpodHRwczovL3NpbXBsZW1hcHMuY29tL2RhdGEvZnItY2l0aWVzCldvcmxkd2lkZSBkYXRhIGNhbiBiZSBhY2Nlc3NlZCBoZXJlOiBodHRwczovL3NpbXBsZW1hcHMuY29tL2RhdGEvd29ybGQtY2l0aWVzCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CmNpdGllcyA8LSByZWFkX2V4Y2VsKCJ3b3JsZGNpdGllcy54bHN4IikKYGBgCgpUaGF0J3MgdG9vIGJpZy4gTGV0J3MgZm9jdXMgb24gR2VybWFueSwgSXRhbHksIEF1c3RyaWEsIFNsb3ZlbmlhIGFuZCBTd2l0emVybGFuZCAoYXJiaXRyYXJpbHkpLgoKYGBge3J9CmNpdGllc19zaG9ydCA8LSBjaXRpZXMgJT4lIAogIGZpbHRlcihjb3VudHJ5ICVpbiUgYygiR2VybWFueSIsICJJdGFseSIsICJTd2l0emVybGFuZCIsICJBdXN0cmlhIiwgIlNsb3ZlbmlhIiksCiAgICAgICAgIHBvcHVsYXRpb24gPiAyNTAwMDApICU+JQogIG5hLm9taXQoKQpoZWFkKGNpdGllc19zaG9ydCkKYGBgCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CndvcmxkIDwtIG1hcF9kYXRhKCJ3b3JsZCIpCmcgPC0gZ2dwbG90KGRhdGEgPSB3b3JsZCkgKyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERhdGEgc291cmNlCiAgZ2VvbV9wb2x5Z29uKGRhdGEgPSB3b3JsZCwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERhcmsgYmFja2dyb3VuZCBvZiB0aGUgd29ybGQKICAgICAgICAgICAgICAgYWVzKHggPSBsb25nLCB5ID0gbGF0LCBncm91cCA9IGdyb3VwKSwKICAgICAgICAgICAgICAgc2l6ZSA9IDAuNSwgY29sb3IgPSAiZ3JleSIpICsgICAgICAgICAgICAgICAgICAgICMgT3V0ZXIgbGluZXMgb2YgcG9seWdvbnMKICBnZW9tX3BvaW50KGRhdGEgPSBjaXRpZXNfc2hvcnQsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgVGhlIHBvaW50cwogICAgICAgICAgICAgYWVzKHggPSBsbmcsIAogICAgICAgICAgICAgICAgIHkgPSBsYXQsIAogICAgICAgICAgICAgICAgIHNpemUgPSBwb3B1bGF0aW9uLCAKICAgICAgICAgICAgICAgICBjb2xvciA9IGNvdW50cnksIAogICAgICAgICAgICAgICAgIGxhYmVsID0gY2l0eSkpICsKICAjc2NhbGVfY29sb3VyX2dyYWRpZW50KGxvdyA9ICIjRkZGQjAwIiwgaGlnaCA9ICIjRkYxQjAwIikgKyAgIyBUaGlzIGxpbmUgaXMgZm9yIGNvbG9yID0gcG9wdWxhdGlvbgogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygiI0ZGRUMwMCIsICIjNjZFMjM0IiwgIiMxQkNCRjciLCAiI0NDNDRGOCIsICIjRkMyMzYxIikpICsKICBjb29yZF9zZih4bGltID0gYyg1LCAxOCksIHlsaW0gPSBjKDM2LCA1NyksIGV4cGFuZCA9IEZBTFNFKSAgICAgICAgICAgICAjIFpvb20gb24gRXVyb3BlCmdncGxvdGx5KGcpCmBgYAoKCiMgT3RoZXIgbWFwcwoKRm9yIHBvaW50cywgaXQncyBlYXN5OiBqdXN0IHVzZSB0aGUgc2FtZSBjb29yZGluYXRlIHN5c3RlbSB2aWEgbG9uZ2l0dWRlICYgbGF0aXR1ZGUuIEl0J3MgbXVjaCBtb3JlIHRyaWNreSBmb3IgYXJlYXMuCgpUaGUga2V5IGlzIHRvIGZpbmQgKipzaGFwZSBmaWxlcyoqIChvbmUgYWx0ZXJuYXRpdmUgZm9ybWF0IGlzIEdlb0pTT04sIGJ1dCBpdCBpcyBub3QgdHJlYXRlZCBoZXJlKS4gIApFeGFtcGxlOiBodHRwOi8vZGF0YXBhZ2VzLmNvbS9naXMtbWFwLXB1Ymxpc2hpbmctcHJvZ3JhbS9naXMtb3Blbi1maWxlcy9nbG9iYWwtZnJhbWV3b3JrLyAKQmUgdmVyeSBjYXJlZnVsOiBzb21lIG1hcHMgYXJlICpFWFRSRU1FTFkqIHByZWNpc2UgKHVwIHRvIDFtIHByZWNpc2lvbikuIFRodXMsIHRoZSBmaWxlcyBhcmUgaGVhdnkgYW5kIHRoZSBwbG90dGluZyB0YWtlcyBhICpMT1QqIG9mIHRpbWUuIEJlbG93LCB3ZSB1c2UgMTAwbSBwcmVjaXNpb24sIHdoaWNoIGlzIGxhcmdlbHkgZW5vdWdoLgoKVGhlIGZ1bGwgZGVzY3JpcHRpb24gb2YgYWxsIHNoYXBlIGZpbGUgaXRlbXMgY2FuIGJlIGFjY2Vzc2VkIG9uIHdpa2lwZWRpYToKCiogLnNocCDigJQgc2hhcGUgZm9ybWF0OyB0aGUgZmVhdHVyZSBnZW9tZXRyeSBpdHNlbGYgICAKKiAuc2h4IOKAlCBzaGFwZSBpbmRleCBmb3JtYXQ7IGEgcG9zaXRpb25hbCBpbmRleCBvZiB0aGUgZmVhdHVyZSBnZW9tZXRyeSB0byBhbGxvdyBzZWVraW5nIGZvcndhcmRzIGFuZCBiYWNrd2FyZHMgcXVpY2tseSAgIAoqIC5kYmYg4oCUIGF0dHJpYnV0ZSBmb3JtYXQ7IGNvbHVtbmFyIGF0dHJpYnV0ZXMgZm9yIGVhY2ggc2hhcGUsIGluIGRCYXNlIElWIGZvcm1hdAoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpyZWdpb25zIDwtIHNmOjpzdF9yZWFkKCJyZWdpb25zLTIwMTQwMzA2LTEwMG0uc2hwIikKc3RyKHJlZ2lvbnMpICAgICMgU3RydWN0dXJlIG9mIHRoZSB2YXJpYWJsZSByZWdpb24KYGBgCgpUaGF0J3MgY29tcGxpY2F0ZWQuIFRoZSBvbmx5IGltcG9ydGFudCBjb2x1bW4gZm9yIG5vdyBpcyB0aGUgKm5hbWUqIG9mIHRoZSByZWdpb24gd2hpY2ggaXMgZWFzeSB0byByZWNvZ25pemUuIEJlbG93LCB3ZSBwbG90IGEgY2hvcm9wbGV0aCB3aGVyZSB0aGUgY29sb3IgaXMgbGluayB0byB0aGUgYXJlYSBvZiB0aGUgcmVnaW9uIChiaWdnZXIgPSBkYXJrZXIpLgoKYGBge3J9CnJlZ2lvbnMgJT4lIAogIHRtX3NoYXBlKCkgKwogIHRtX3BvbHlnb25zKCJzdXJmX2ttMiIpCmBgYAoKVGhhdCdzIHByZXR5IHVnbHkuIExldCdzIHJlbW92ZSB0aGUgcmVnaW9ucyBvdXRzaWRlIHRoZSBtYWlubGFuZC4gCgpgYGB7cn0KcmVnaW9ucyAlPiUKICBmaWx0ZXIoISAobm9tICVpbiUgYygiR3V5YW5lIiwgIk1heW90dGUiLCAiTWFydGluaXF1ZSIsICJHdWFkZWxvdXBlIiwgIkxhIFLDqXVuaW9uIikpKSAlPiUgCiAgdG1fc2hhcGUoKSArCiAgdG1fcG9seWdvbnMoInN1cmZfa20yIiwgYm9yZGVyLmNvbCA9ICJ3aGl0ZSIpICMgV2l0aCB0aGUgYm9yZGVyIG9mIHBvbHlnb25zCmBgYAoKQmVsb3csIHdlIGNoYW5nZSB0aGUgY29sb3IgcGFsZXR0ZSBhbmQgcGxvdCB0aGUgbnVtYmVyIG9mIEZyZW5jaCAiZMOpcGFydGVtZW50cyIgaW5zaWRlIGVhY2ggcmVnaW9uLgoKYGBge3J9CnJlZ2lvbnMgJT4lCiAgZmlsdGVyKCEgKG5vbSAlaW4lIGMoIkd1eWFuZSIsICJNYXlvdHRlIiwgIk1hcnRpbmlxdWUiLCAiR3VhZGVsb3VwZSIsICJMYSBSw6l1bmlvbiIpKSkgJT4lIAogIHRtX3NoYXBlKCkgKwogIHRtX2ZpbGwoIm5iX2RlcCIsIHBhbGV0dGUgPSAiU3BlY3RyYWwiKSAgIyBXaXRob3V0IHRoZSBib3JkZXIKYGBgCgpUaGlzIGlzIHN0YXRpYzogKmxlYWZsZXQqIGludGVncmF0aW9uIHdvdWxkIGJlIGJldHRlci4KCiMgSGlnaGNoYXJ0cwoKVGhlIG1hdGVyaWFsIGJlbG93IGlzIGluc3BpcmVkIGZyb20gdGhlIHZpZ25ldHRlIGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9oaWdoY2hhcnRlci92aWduZXR0ZXMvY2hhcnRpbmctbWFwcy5odG1sCgpUaGUgKmhpZ2hjaGFydGVyKiBwYWNrYWdlIGNyZWF0ZXMgZ29vZCBsb29raW5nIHBsb3RzLiBMZXQncyBpbnN0YWxsIGl0IChhbmQgYWN0aXZhdGUgaXQpLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IFRSVUV9CmlmKCFyZXF1aXJlKGhpZ2hjaGFydGVyKSl7aW5zdGFsbC5wYWNrYWdlcygiaGlnaGNoYXJ0ZXIiKX0KbGlicmFyeShoaWdoY2hhcnRlcikKYGBgCgpUaGUgcGFja2FnZSBoYXMgYSBsYXJnZSBsaWJyYXJ5IG9mIG1hcHMuIFRoZSBsaXN0IGlzIGdpdmVuIGhlcmU6Cmh0dHBzOi8vY29kZS5oaWdoY2hhcnRzLmNvbS9tYXBkYXRhLwoKSWYgeW91IGNsaWNrIG9uIGEgbGluaywgeW91IGdldCB0aGUgYWRkcmVzcyBvZiB0aGUgbWFwOgoKIVtdKGhpZ2hfbGlzdC5wbmcpCgpUaGUgbGFzdCBwYXJ0IGlzIHdoYXQgeW91IHNwZWNpZnkgdG8gZ2V0IGFjY2VzcyB0byBhIG1hcDoKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KbWFwZGF0YSA8LSBnZXRfZGF0YV9mcm9tX21hcChkb3dubG9hZF9tYXBfZGF0YSgiY291bnRyaWVzL2ZyL2N1c3RvbS9mci1hbGwtbWFpbmxhbmQiKSkKaGVhZChtYXBkYXRhKQpgYGAKClRoZSBpbmZvcm1hdGlvbiB0aGF0IHdpbGwgYmUgcGxvdHRlZCBjb21lcyBmcm9tIHRoaXMgZmlsZS4gV2UgbmVlZCB0byBhZGQgdGhlIHZhbHVlIHRoYXQgd2Ugd2FudCB0byB1c2UgZm9yIHRoZSBwbG90LiBUaGVyZSBhcmUgMjEgcmVnaW9ucyBpbiB0aGUgdmFyaWFibGUgKm1hcF9kYXRhKi4gVGhpcyBieXBhc3NlcyB0aGUgbWVyZ2luZyBvZiBleHRlcm5hbCBkYXRhIHZpYSB0aGUgKipsZWZ0X2pvaW4qKigpIGZ1bmN0aW9uLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQptYXBkYXRhIDwtIG1hcGRhdGEgJT4lIG11dGF0ZSh2YWx1ZSA9IDE6bnJvdyhtYXBkYXRhKSkgIyBDb21wbGV0ZWx5IHJhbmRvbSB2YWx1ZXMKaGVhZChtYXBkYXRhLCAyMCkKYGBgCgpCZWxvdywgd2UgcGxvdCB0aGUgcmVnaW9ucyBvZiBGcmFuY2UgYW5kIHRoZSBjb2xvciBjb2RlIGlzIGRyaXZlbiBieSB0aGUgY29sdW1uICJ2YWx1ZSIgZGVmaW5lZCBhYm92ZS4gCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CmhjbWFwKCJjb3VudHJpZXMvZnIvY3VzdG9tL2ZyLWFsbC1tYWlubGFuZCIsICAgICAgICAgICAgICAgICAgICAgICMgU291cmNlIGZvciB0aGUgbWFwCiAgICAgIGRhdGEgPSBtYXBkYXRhLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgU291cmNlIGZvciB0aGUgY29sb3IgY29kZQogICAgICB2YWx1ZSA9ICJ2YWx1ZSIsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIENvbHVtbiBmb3IgY29sb3IgY29kZQogICAgICBqb2luQnkgPSBjKCJuYW1lIiksICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIENvbW1vbiBuYW1lICYgbGFiZWwKICAgICAgbmFtZSA9ICJSYW5kb20gUGxvdCIsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBOYW1lIG9mIHBsb3QKICAgICAgZGF0YUxhYmVscyA9IGxpc3QoZW5hYmxlZCA9IFRSVUUsIGZvcm1hdCA9ICJ7cG9pbnQubmFtZX0iKSwgIyBBbGxvdyBsYWJlbHMKICAgICAgYm9yZGVyQ29sb3IgPSAiI0ZBRkFGQSIsIGJvcmRlcldpZHRoID0gMC41LCAgICAgICAgICAgICAgICAgIyBCb3JkZXIgaW5mbwogICAgICB0b29sdGlwID0gbGlzdCh2YWx1ZURlY2ltYWxzID0gMSwgICAgICAgICAgICAgICAgICAgICAgICAgICAjIExhYmVsIGluZm8KICAgICAgICAgICAgICAgICAgICAgdmFsdWVQcmVmaXggPSAiVmFsdWUgPSAiLCAgICAgICAgICAgICAgICAgICAgIyBCZWZvcmUgbGFiZWwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVTdWZmaXggPSAiIFVuaXRzIikpICAgICAgICAgICAgICAgICAgICAgIyBBZnRlciBsYWJlbApgYGAKCgojIEZpbmUgZ3JhaW4gbG9jYWwgbWFwcwoKTm93IGxldCdzIHR1cm4gdG8gc21hbGxlciBzY2FsZSBtYXBzLiBUaGUgZGF0YSBjb21lcyBmcm9tIEthZ2dsZSwgc2NyYXBwZWQgZnJvbSB0cmlwYWR2aXNvci4gVGhlIHVzZWZ1bCBjb2x1bW5zOiAqTG9uZ2l0dWRlKiAmICpMYXRpdHVkZSosIHRoZSAqTXVzZXVtTmFtZSosIHRoZSAqUmF0aW5nKiBhbmQgdGhlICpMZW5ndGhPZlZpc2l0Ki4gCgpXaXRoIGxlYWZsZXQsIGl0J3MgZWFzeSB0byBvYnRhaW4gZW1wdHkgbWFwcyBhcyBsb25nIGFzIHlvdSBrbm93IHRoZSBsb25naXR1ZGUgJiBsYXRpdHVkZSBvZiBhIHBhcnRpY3VsYXIgbG9jYXRpb24uIEJlbG93OiAqKkx5b24qKi4KCmBgYHtyfQp0cmlwYWR2aXNvciA8LSByZWFkX2V4Y2VsKCJ0cmlwYWR2aXNvcl9tdXNldW1fVVMueGxzeCIpICU+JSAgICAjIEltcG9ydGluZyB0aGUgZGF0YQogIG11dGF0ZShMb25naXR1ZGUgPSBhcy5udW1lcmljKExvbmdpdHVkZSksICAgICAgICAgICAgICAgICAgICAjIEdldHRpbmcgbnVtYmVycyBiYWNrCiAgICAgICAgIExhdGl0dWRlID0gYXMubnVtZXJpYyhMYXRpdHVkZSksCiAgICAgICAgIFJhdGluZyA9IGFzLm51bWVyaWMoUmF0aW5nKSwKICAgICAgICAgTGVuZ3RoT2ZWaXNpdCA9IGFzLmZhY3RvcihMZW5ndGhPZlZpc2l0KSkgJT4lCiAgZmlsdGVyKExhdGl0dWRlID4gNDAsIExhdGl0dWRlIDwgNDEsIExvbmdpdHVkZSA+ICgtNzQuNSksIExvbmdpdHVkZSA8ICgtNzMuNSkpICMgTlkgbXVzZXVtcyBvbmx5CnRyaXBhZHZpc29yCgpsZWFmbGV0KCkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEFuIGVtcHR5IG1hcDogYSBnb29kIHN0YXJ0IQogIHNldFZpZXcobG5nID0gNC44NSwgbGF0ID0gNDUuNzUsIHpvb20gPSAxMikgJT4lIAogIGFkZFRpbGVzKCkKYGBgCgpgYGB7cn0KcGFsIDwtIGNvbG9yRmFjdG9yKHBhbGV0dGUgPSAiU3BlY3RyYWwiLCBkb21haW4gPSB0cmlwYWR2aXNvciRMZW5ndGhPZlZpc2l0KQp0cmlwYWR2aXNvciAlPiUKICBsZWFmbGV0KCkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBBIGJsYW5rIHNoZWV0Li4uCiAgc2V0VmlldyhsbmcgPSAtNzMuOTc3MiwgbGF0ID0gNDAuNzgwOCwgem9vbSA9IDEwKSAlPiUgCiAgYWRkVGlsZXMoKSAlPiUKICBhZGRDaXJjbGVzKGxuZyA9IH5Mb25naXR1ZGUsIGxhdCA9IH5MYXRpdHVkZSwgCiAgICAgICAgICAgICByYWRpdXMgPSB+UmF0aW5nXjQsIGxhYmVsID0gIH5NdXNldW1OYW1lLAogICAgICAgICAgICAgZmlsbENvbG9yID0gfnBhbChMZW5ndGhPZlZpc2l0KSwgZmlsbE9wYWNpdHkgPSAwLjksICAjIENpcmNsZSBjb2xvcnMKICAgICAgICAgICAgIHN0cm9rZSA9IFRSVUUsIGNvbG9yID0gImJsYWNrIiwgd2VpZ2h0ID0gMikgJT4lICAgICAgIyBDaXJjbGUgc3Ryb2tlCiAgYWRkTGVnZW5kKHBhbCA9IHBhbCwgICAgICAgICAgICAgICAgICAgICAgIyBMZWdlbmQ6IGNvbWVzIGZyb20gcGFsZXQgY29sb3JzIGRlZmluZWQgYWJvdmUKICAgICAgICAgICAgdmFsdWVzID0gfkxlbmd0aE9mVmlzaXQsICAgICAgICAjIFZhbHVlcyBjb21lIGZyb20gbGlmZUV4cCB2YXJpYWJsZQogICAgICAgICAgICBvcGFjaXR5ID0gMC43LCAgICAgICAgICAgICAgICAgICMgT3BhY2l0eSBvZiBsZWdlbmQKICAgICAgICAgICAgdGl0bGUgPSAiTWFwIExlZ2VuZCIsICAgICAgICAgICAjIFRpdGxlIG9mIGxlZ2VuZAogICAgICAgICAgICBwb3NpdGlvbiA9ICJib3R0b21yaWdodCIpICAgICAgICMgUG9zaXRpb24gb2YgbGVnZW5kCmBgYAoKQmVsb3csIHlvdSBuZWVkIHRvIHNwZWNpZnkgKipsb24qKiBhbmQgKipsYXQqKiBpbiB0aGUgc291cmNlIGRhdGFmcmFtZS4gT3IgeW91IGNhbiB1c2UgdGhlICpoY2FlcyooKSBmdW5jdGlvbgoKYGBge3J9Cm1hcGRhdGEyIDwtIGdldF9kYXRhX2Zyb21fbWFwKGRvd25sb2FkX21hcF9kYXRhKCJjb3VudHJpZXMvdXMvY3VzdG9tL3VzLW55LWNvbmdyZXNzLTExMyIpKQpoY21hcCgiY291bnRyaWVzL3VzL2N1c3RvbS91cy1ueS1jb25ncmVzcy0xMTMiKSAlPiUKICBoY19tYXBOYXZpZ2F0aW9uKGVuYWJsZWQgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgYnV0dG9uT3B0aW9ucyA9IGxpc3QoCiAgICAgICAgICAgICAgICAgICAgIGFsaWduID0gInJpZ2h0IiwKICAgICAgICAgICAgICAgICAgICAgdmVydGljYWxBbGlnbiA9ICJib3R0b20iKSkgJT4lCiAgaGNfYWRkX3NlcmllcyhkYXRhID0gdHJpcGFkdmlzb3IgJT4lIG11dGF0ZShsb24gPSBMb25naXR1ZGUsIGxhdCA9IExhdGl0dWRlLCBuYW1lID0gTXVzZXVtTmFtZSksICAjIEFkZCBjb3JyZWN0IG5hbWVzCiAgICAgICAgICAgICAgICB0eXBlID0gIm1hcHBvaW50IiwgbmFtZSA9ICJNdXNldW1zIikgCgpgYGAKCk5vdCBhcyBpbXByZXNzaXZlIGFzIGxlYWZsZXQsIGJ1dCB0aGUgbWFwIGNvdWxkIHByb2JhYmx5IGJlIGltcHJvdmVkLiAKKipOT1RFKio6IGluIHRoZSBleGFtcGxlIGFib3ZlLCB0aGUgZ2VvZ3JhcGhpY2FsIGJhY2tncm91bmQgYW5kIHRoZSBwb2ludHMgYXJlIHVucmVsYXRlZC4KCiMgUmVzb3VyY2VzCgpCZWxvdywgYSBsaW5rIG9mIGdyZWF0IHBhZ2VzICYgdHV0b3JpYWxzOgoKKipHZW9jb21wdXRhdGlvbiB3aXRoIFIqKiAob25saW5lIGJvb2spOiBodHRwczovL2dlb2NvbXByLnJvYmlubG92ZWxhY2UubmV0ICAgIAoqKmdncGxvdCoqITogaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL21hcF9kYXRhLmh0bWxQdXJlICAgICAKKipzZioqIChzaGFwZSBmaWxlcyk6IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9zZi92aWduZXR0ZXMvc2YxLmh0bWwgICAKKipzZiAmIFIqKjogaHR0cHM6Ly9yLXNwYXRpYWwuZ2l0aHViLmlvL3NmL2FydGljbGVzL3NmMS5odG1sICAgCioqc2YgJiBnZ3Bsb3QqKjogaHR0cHM6Ly93d3cuci1zcGF0aWFsLm9yZy9yLzIwMTgvMTAvMjUvZ2dwbG90Mi1zZi0yLmh0bWwgICAKKipsaXN0IG9mIHNmKio6IGh0dHA6Ly9kYXRhcGFnZXMuY29tL2dpcy1tYXAtcHVibGlzaGluZy1wcm9ncmFtL2dpcy1vcGVuLWZpbGVzL2dsb2JhbC1mcmFtZXdvcmsvZ2xvYmFsLWhlYXQtZmxvdy1kYXRhYmFzZS9zaGFwZWZpbGVzLWxpc3QgIAoqKnRtYXAqKjogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3RtYXAvdmlnbmV0dGVzL3RtYXAtZ2V0c3RhcnRlZC5odG1sICAgCioqZ2dtYXAqKjogaHR0cHM6Ly9naXRodWIuY29tL2RrYWhsZS9nZ21hcCAgICAKKipsZWFmbGV0Kio6IGh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vbGVhZmxldC8gICAgCioqT3JlZ29uKiogKHR1dG9yaWFsKTogaHR0cDovL2dlb2cudW9yZWdvbi5lZHUvYmFydGxlaW4vY291cnNlcy9nZW9nNDk1L2xlYzA2Lmh0bWwgICAgCgoKCgojIEEgdGVzdCB3aXRoIEdlb0pTT04KCkdlb0pTT04gYXJlLCB3aXRoIHNoYXBlZmlsZXMgdGhlIG90aGVyIG1haW4gc3RhbmRhcmQgaW4gZ2VvY29tcHV0aW5nLiAgIApJdCdzIGVhc3kgdG8gb2J0YWluIGZpbGVzLCBlLmcuLCB2aWEgaHR0cHM6Ly9nZW9qc29uLW1hcHMuYXNoLm1zCkFsc28sIHlvdSB3aWxsIG5lZWQgdGhlICoqcmdkYWwqKiBwYWNrYWdlLgoKCmBgYHtyLCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CmxpYnJhcnkocmdkYWwpCmFmcmljYSA8LSByZ2RhbDo6cmVhZE9HUigiYWZyaWNhLmdlby5qc29uIikKYGBgCmBgYHtyfQpwYWwgPC0gY29sb3JOdW1lcmljKCJ2aXJpZGlzIiwgTlVMTCkKCmxlYWZsZXQoYWZyaWNhKSAlPiUKICBhZGRUaWxlcygpICU+JQogIGFkZFBvbHlnb25zKGZpbGxPcGFjaXR5ID0gMC43LAogICAgICAgICAgICAgIGZpbGxDb2xvciA9IH5wYWwocG9wX2VzdCksCiAgICAgICAgICAgICAgd2VpZ2h0ID0gMiwKICAgICAgICAgICAgICBvcGFjaXR5ID0gMSwKICAgICAgICAgICAgICBjb2xvciA9ICJ3aGl0ZSIpICU+JQogIGFkZExlZ2VuZChwYWwgPSBwYWwsIHZhbHVlcyA9IH5wb3BfZXN0LCBvcGFjaXR5ID0gMC43LCB0aXRsZSA9IE5VTEwsCiAgICBwb3NpdGlvbiA9ICJib3R0b21sZWZ0IikKYGBgCgo=